/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package mylist;

import interfaces.MyMap;
import java.util.Iterator;
import java.util.NoSuchElementException;

/**
 *
 * @author Daryna_Ragimova
 */
public class MyHashMap<K,V> implements MyMap<K,V>{
    
    static final int DEFAULT_INITIAL_CAPACITY = 16;
    static final int MAXIMUM_CAPACITY = 1 << 30;
    static final float DEFAULT_LOAD_FACTOR = 0.75f;
    private float loadFactor;
    
    private SimpleEntry[] elements;

    /**
     * The number of key-value mappings contained in this map.
     */
    int size = 0;
    
    public MyHashMap(int initialCapacity, float loadFactor) {
        if (initialCapacity < 0)
            throw new IllegalArgumentException("Illegal initial capacity");
        if (initialCapacity > MAXIMUM_CAPACITY)
            initialCapacity = MAXIMUM_CAPACITY;
        if (loadFactor <= 0)
            throw new IllegalArgumentException("Illegal load factor: ");

        this.loadFactor = loadFactor;
        elements = new SimpleEntry[initialCapacity];
    }
    public MyHashMap(int initialCapacity) {
        this(initialCapacity, DEFAULT_LOAD_FACTOR);
    }

    
    public MyHashMap() {
        this.loadFactor = DEFAULT_LOAD_FACTOR;
        elements = new SimpleEntry[DEFAULT_INITIAL_CAPACITY];
   }
    
    class SimpleEntry<K,V> implements Entry<K,V>{
        
        final K key;
        V value;
        SimpleEntry<K,V> next;
        final int hash;
        
        SimpleEntry(int h, K k, V v, SimpleEntry<K,V> n) {
            value = v;
            next = n;
            key = k;
            hash = h;
        }
        
        @Override
        public boolean equals(Object o){
            if (!(o instanceof MyMap.Entry))
                return false;
            MyMap.Entry<K,V> entry = (MyMap.Entry)o;
            Object k1 = getKey();
            Object k2 = entry.getKey();
            if (k1 == k2 || (k1 != null && k1.equals(k2))) {
                Object v1 = getValue();
                Object v2 = entry.getValue();
                if (v1 == v2 || (v1 != null && v1.equals(v2)))
                    return true;
            }
            return false;
        }
        
        @Override
        public K getKey() {
            return key;
        }

        @Override
        public V getValue() {
            return value;
        }

        @Override
        public V setValue(V value) {
            V oldValue = this.value;
            this.value = value;
            return oldValue;
        }
        
        @Override
        public final int hashCode() {
            return (key==null   ? 0 : key.hashCode()) ^
                   (value==null ? 0 : value.hashCode());
        }
    }
     /**
     * Returns index for hash code h.
     */
    static int indexFor(int h, int length) {
        return h & (length-1);
    }
    
    static int hash(int h) {
        h ^= (h >>> 20) ^ (h >>> 12);
        return h ^ (h >>> 7) ^ (h >>> 4);
    }
    
    @Override
    public void clear() {
        for (int i = 0; i < elements.length; i++){
            elements[i] = null;
        }
        size = 0;
    }

    @Override
    public boolean containsKey(Object key) { 
        int hash;
        if (key == null){
            hash = 0;
        }else{
            hash = hash(key.hashCode());
        }
        SimpleEntry<K,V> entity = elements[indexFor(hash, elements.length)];
        while (entity != null) {
            Object k = entity.key;
            if (entity.hash == hash && (k == key || (key != null && key.equals(k))))
                return true;
            entity = entity.next;
        }
        return false;
    }

    
    @Override
    public boolean containsValue(Object value) { 
        if (value == null){
            for (int i = 0; i < elements.length; i++){
                SimpleEntry<K,V> e = elements[i];
                while (e != null){
                    if (e.getValue() == null){
                        return true;
                    }
                    e = e.next;
                }
            }
            return false;
        }
        for (int i = 0; i < elements.length; i++){
            SimpleEntry<K,V> e = elements[i];
            while (e != null){
                if (e.getValue().equals(value)){
                    return true;
                }
                e = e.next;
            }
        }
        return false;
    }

    @Override
    public V get(Object key) { 
        if (key == null){
           SimpleEntry<K,V> e = elements[0];
           while (e != null){
                if (e.key == null){
                    return e.value;
                }
                e = e.next;
            }
            return null;
        }
        int hash = hash(key.hashCode());
        int indexInMap = indexFor(hash,elements.length);
        SimpleEntry<K,V> e = elements[indexInMap];
        while (e != null){
            if ((hash == e.hash) && (e.key == key || e.key.equals(key))){
                return e.value;
            }
            e = e.next;
        }
        return null;
        
    }

    @Override
    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public V put(K key, V value) { 
        if (key == null){
            return putIfKeyIsNull(value);
        }
        int hash = hash(key.hashCode());
        int indexInMap= indexFor(hash,elements.length);
        SimpleEntry<K,V> e = elements[indexInMap];
        while (e != null){
            if ((hash == e.hash) && (e.key == key || e.key.equals(key))){
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
            e = e.next;
        }
        putNewEntry(hash, key, value, indexInMap);
        return null;
    }

    @Override
    public V remove(K key) { 
        int hash = hash(key.hashCode());
        int i = indexFor(hash,elements.length);
        SimpleEntry<K,V> previous  = elements[i];
        SimpleEntry<K,V> e = previous;
        while(e != null){
            SimpleEntry<K,V> next = e.next;
            if ((hash == e.hash) && (e.key == key || e.key.equals(key))){
                size--;
                if (previous == e){
                    elements[i] = next;
                }
                else{
                    previous.next = next;
                }
                return e.value;
            }
            previous = e;
            e = e.next;
        }
        return null;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public Iterator<MyMap.Entry<K,V>> entryIterator() {
        return new EntryIterator();
    }
    
    private void putNewEntry(int hash, K key, V value,int i) {
        SimpleEntry<K,V> e = elements[i];
        elements[i] = new SimpleEntry<K,V>(hash, key, value, e);
        size++;
    }

    private V putIfKeyIsNull(V value) {
        SimpleEntry<K,V> e = elements[0];
        while (e != null){
            if (e.key == null){
                V oldValue = e.value;
                e.value = value;
                return oldValue;
            }
            e = e.next;
        }
        putNewEntry(0, null, value, 0);
        return null;
    }

    
    private abstract class MyHashIterator<E> implements Iterator<E> {
        SimpleEntry<K,V> next;	
        int index;		// current 
        SimpleEntry<K,V> current;	

        MyHashIterator() {
            skipEmpty();
        }
        private void skipEmpty(){
            if (size > 0){
                SimpleEntry[] t = elements;
                while (index < t.length && (next = t[index++]) == null);
            }
        }
        
        @Override
        public final boolean hasNext() {
            return next != null;
        }

        final Entry<K,V> nextEntry() {
            SimpleEntry<K,V> e = next;
            if (e == null)
                throw new NoSuchElementException();
            next = e.next;
            if (next == null) {
                skipEmpty();
            }
	    current = e;
            return e;
        }

        @Override
        public void remove() {
            if (current == null)
                throw new IllegalStateException();
            MyHashMap.this.remove(current.key);
            current = null;
        }

    }
    private final class EntryIterator extends MyHashIterator<MyMap.Entry<K,V>> {
        @Override
        public MyMap.Entry<K,V> next() {
            return nextEntry();
        }
    }
}
